package alipay

import (
	"bytes"
	"context"
	"crypto/rsa"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"time"
)

const alipayGateway = "https://openapi.alipay.com/gateway.do?charset=utf-8"

// AlipayClient alipay client
type AlipayClient struct {
	// 开发者应用id
	AppID string
	// 签名类 RSA/RSA2
	signType string
	// 服务商/商家私钥
	PrivateKey []byte
	privateKey *rsa.PrivateKey
	// 支付宝公钥
	AliPayPublicKey []byte
	publicKey       *rsa.PublicKey
}

// 创建client
func NewAliPayClient(AppID, PrivateKey, AliPayPublicKey string) *AlipayClient {
	return &AlipayClient{
		AppID:           AppID,
		PrivateKey:      []byte(PrivateKey),
		AliPayPublicKey: []byte(AliPayPublicKey),
	}
}

// 参数构建
func (a *AlipayClient) buildParam(apiName, appAuthToken string, bizContent map[string]interface{}, needMerge bool) (map[string]interface{}, error) {
	param := map[string]interface{}{
		"app_id":    a.AppID,
		"method":    apiName,
		"format":    "JSON",
		"charset":   "utf-8",
		"sign_type": a.getSignType(),
		"timestamp": a.getTimestamp(),
		"version":   "1.0",
	}
	if appAuthToken != "" {
		param["app_auth_token"] = appAuthToken
	}
	if needMerge {
		for k, v := range bizContent {
			param[k] = v
		}
	} else {
		bc, err := json.Marshal(bizContent)
		if err != nil {
			return nil, err
		}
		param["biz_content"] = string(bc)
	}
	sign, err := a.Sign(param)
	if err != nil {
		return nil, err
	}
	param["sign"] = sign
	return param, err
}

// url encode
func (a *AlipayClient) queryUrlEncode(param map[string]interface{}) []byte {
	query := url.Values{}
	for k, v := range param {
		query.Add(k, a.fieldToString(v))
	}
	return []byte(query.Encode())
}

// multipart 表单数据构建
func (a *AlipayClient) multipartData(param map[string]interface{}) (*bytes.Buffer, string, error) {
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	for key, r := range param {
		var fw io.Writer
		var err error
		if x, ok := r.(io.Closer); ok {
			defer func() {
				_ = x.Close()
			}()
		}
		if f, ok := r.(*FileData); ok {
			if f == nil {
				if err = w.WriteField(key, ""); err != nil {
					return nil, "", err
				}
				continue
			}
			if fw, err = w.CreateFormFile(key, f.Name()); err != nil {
				return nil, "", err
			}
			if _, err = io.Copy(fw, f.File()); err != nil {
				return nil, "", err
			}
		} else if x, ok := r.(*os.File); ok {
			if x == nil {
				if err = w.WriteField(key, ""); err != nil {
					return nil, "", err
				}
				continue
			}
			if fw, err = w.CreateFormFile(key, x.Name()); err != nil {
				return nil, "", err
			}
			if _, err = io.Copy(fw, x); err != nil {
				return nil, "", err
			}
		} else if t, ok := r.(io.Reader); ok {
			if fw, err = w.CreateFormField(key); err != nil {
				return nil, "", err
			}
			if _, err = io.Copy(fw, t); err != nil {
				return nil, "", err
			}
		} else {
			if err = w.WriteField(key, a.fieldToString(r)); err != nil {
				return nil, "", err
			}
		}
	}
	_ = w.Close()
	return &b, w.FormDataContentType(), nil
}

func (a *AlipayClient) _response(resp *http.Response) (map[string]interface{}, error) {
	if resp == nil {
		return nil, errors.New("返回结果为空")
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	err = a.ResponseVerify(body)
	if err != nil {
		return nil, err
	}
	body, err = a.decodeResp(resp.Header.Get("Content-Type"), body)
	if err != nil {
		return nil, err
	}
	res := map[string]interface{}{}
	err = json.Unmarshal(body, &res)
	if err != nil {
		return nil, err
	}
	for k, v := range res {
		if k == "sign" {
			continue
		}
		r, ok := v.(map[string]interface{})
		if ok {
			return r, nil
		}
	}
	return nil, nil
}

// 发送请求， needMerge 表示 bit_content 是否独立字段存在
func (a *AlipayClient) sendRequest(ctx context.Context, apiName, appAuthToken string, bizContent map[string]interface{}, retry int, needMerge bool) (map[string]interface{}, error) {
	param, err := a.buildParam(apiName, appAuthToken, bizContent, needMerge)
	if err != nil {
		return nil, err
	}
	query := a.queryUrlEncode(param)
	if retry == 0 {
		retry = 1
	}
	var resp *http.Response
	client := &http.Client{Timeout: 20 * time.Second}
	for {
		if retry <= 0 {
			break
		}
		req, err := http.NewRequestWithContext(ctx, "POST", alipayGateway, bytes.NewBuffer(query))
		if err != nil {
			return nil, err
		}
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		resp, err = client.Do(req)
		if err != nil || resp.StatusCode == http.StatusInternalServerError {
			time.Sleep(200 * time.Millisecond)
			retry--
		} else {
			break
		}
	}
	return a._response(resp)
}

// 表单形式提交
func (a *AlipayClient) sendRequestV2(ctx context.Context, apiName, appAuthToken string, bizContent map[string]interface{}, retry int, needMerge bool) (map[string]interface{}, error) {
	param, err := a.buildParam(apiName, appAuthToken, bizContent, needMerge)
	if err != nil {
		return nil, err
	}
	buf, contentType, err := a.multipartData(param)
	if err != nil {
		return nil, err
	}
	if retry == 0 {
		retry = 1
	}
	var resp *http.Response
	client := &http.Client{Timeout: 20 * time.Second}
	for {
		if retry <= 0 {
			break
		}
		req, err := http.NewRequestWithContext(ctx, "POST", alipayGateway, buf)
		if err != nil {
			return nil, err
		}
		req.Header.Set("Content-Type", contentType)
		resp, err = client.Do(req)
		if err != nil || resp.StatusCode == http.StatusInternalServerError {
			time.Sleep(200 * time.Millisecond)
			retry--
		} else {
			break
		}
	}
	return a._response(resp)
}

func (a *AlipayClient) commonErrHandler(err error, resp map[string]interface{}) error {
	if err != nil {
		return err
	}
	if code, ok := resp["code"]; ok && code != "10000" {
		return fmt.Errorf("code:%s;msg:%s;sub_code:%s;sub_msg:%s", resp["code"], resp["msg"], resp["sub_code"], resp["sub_msg"])
	}
	return nil
}

// 开启代商户签约事务
func (a *AlipayClient) AgentCreate(ctx context.Context, p *AgentCreateReq) (*AgentCreateResp, error) {
	const method = "alipay.open.agent.create"
	if p == nil {
		return nil, nil
	}
	param := map[string]interface{}{
		"account": p.Account,
	}
	if p.OrderTicket != "" {
		param["order_ticket"] = p.OrderTicket
	}
	param["contact_info"] = map[string]interface{}{
		"contact_name":   p.ContactInfo.ContactName,
		"contact_mobile": p.ContactInfo.ContactMobile,
		"contact_email":  p.ContactInfo.ContactEmail,
	}
	resp, err := a.sendRequest(ctx, method, "", param, 2, false)
	if err = a.commonErrHandler(err, resp); err != nil {
		return nil, err
	}
	return &AgentCreateResp{
		BatchNo:     resp["batch_no"].(string),
		BatchStatus: resp["batch_status"].(string),
	}, nil
}

// 提交代商户签约事务
func (a *AlipayClient) AgentConfirm(ctx context.Context, p *AgentConfirmReq) (*AgentConfirmResp, error) {
	const method = "alipay.open.agent.confirm"
	if p == nil || p.BatchNo == "" {
		return nil, nil
	}
	param := map[string]interface{}{"batch_no": p.BatchNo}
	resp, err := a.sendRequest(ctx, method, "", param, 2, false)
	if err = a.commonErrHandler(err, resp); err != nil {
		return nil, err
	}
	return &AgentConfirmResp{
		UserId:          parseString(resp["user_id"]),
		AuthAppId:       parseString(resp["auth_app_id"]),
		AppAuthToken:    parseString(resp["app_auth_token"]),
		AppRefreshToken: parseString(resp["app_refresh_token"]),
		ExpiredIn:       parseString(resp["expires_in"]),
		ReExpiredIn:     parseString(resp["re_expires_in"]),
	}, nil
}

// 取消代商户签约事务
func (a *AlipayClient) AgentCancel(ctx context.Context, p *AgentCancelReq) error {
	const method = "alipay.open.agent.cancel"
	if p == nil || p.BatchNo == "" {
		return nil
	}
	param := map[string]interface{}{"batch_no": p.BatchNo}
	resp, err := a.sendRequest(ctx, method, "", param, 2, false)
	return a.commonErrHandler(err, resp)
}

// 查询代签约状态
func (a *AlipayClient) AgentFaceToFaceSign(ctx context.Context, p *AgentFaceToFaceSignReq) error {
	const method = "alipay.open.agent.facetoface.sign"
	if p == nil {
		return nil
	}
	param := map[string]interface{}{
		"batch_no":                  p.BatchNo,
		"mcc_code":                  p.MccCode,
		"special_license_pic":       p.SpecialLicensePic,
		"rate":                      p.Rate,
		"sign_and_auth":             p.SignAndAuth,
		"business_license_no":       p.BusinessLicenseNo,
		"business_license_pic":      p.BusinessLicensePic,
		"business_license_auth_pic": p.BusinessLicenseAuthPic,
		"long_term":                 p.LongTerm,
		"date_limitation":           p.DateLimitation,
		"shop_scene_pic":            p.ShopScenePic,
		"shop_sign_board_pic":       p.ShopSignBoardPic,
	}
	resp, err := a.sendRequestV2(ctx, method, "", param, 2, true)
	return a.commonErrHandler(err, resp)
}

func (a *AlipayClient) AgentOrderQuery(ctx context.Context, p *AgentOrderQueryReq) (*AgentOrderQueryResp, error) {
	const method = "alipay.open.agent.order.query"
	if p == nil || p.BatchNo == "" {
		return nil, nil
	}
	param := map[string]interface{}{"batch_no": p.BatchNo}
	resp, err := a.sendRequest(ctx, method, "", param, 2, false)
	if err = a.commonErrHandler(err, resp); err != nil {
		return nil, err
	}
	return &AgentOrderQueryResp{
		OrderStatus:  parseString(resp["order_status"]),
		ConfirmUrl:   parseString(resp["confirm_url"]),
		AgentAppId:   parseString(resp["agent_app_id"]),
		RejectReason: parseString(resp["reject_reason"]),
		MerchantPid:  parseString(resp["merchant_pid"]),
	}, nil
}

// 图片上传
func (a *AlipayClient) MaterialImageUpload(ctx context.Context, p *MaterialImageUploadReq) (*MaterialImageUploadResp, error) {
	const method = "alipay.offline.material.image.upload"
	if p == nil {
		return nil, nil
	}
	param := map[string]interface{}{
		"image_type":    p.ImageType,
		"image_name":    p.ImageName,
		"image_content": p.ImageContent,
	}
	if p.ImagePid != "" {
		param["image_pid"] = p.ImagePid
	}
	resp, err := a.sendRequestV2(ctx, method, "", param, 2, true)
	if err = a.commonErrHandler(err, resp); err != nil {
		return nil, err
	}
	return &MaterialImageUploadResp{
		ImageId:  parseString(resp["image_id"]),
		ImageUrl: parseString(resp["image_url"]),
	}, nil
}

// 查询商户某个产品的签约状态
func (a *AlipayClient) AgentSignStatusQuery(ctx context.Context, p *AgentSignStatusQueryReq) (*AgentSignStatusQueryResp, error) {
	const method = "alipay.open.agent.signstatus.query"
	if p == nil {
		return nil, nil
	}
	if !p.IsValidCodes() {
		return nil, errors.New("invalid product codes")
	}
	param := map[string]interface{}{
		"pid":           p.Pid,
		"product_codes": p.ProductCodes,
	}
	resp, err := a.sendRequest(ctx, method, p.AppAuthToken, param, 2, false)
	if err = a.commonErrHandler(err, resp); err != nil {
		return nil, err
	}
	sl, ok := resp["sign_status_list"]
	if !ok {
		return nil, errors.New("invalid response")
	}
	statusList, ok := sl.([]interface{})
	if !ok {
		return nil, errors.New("invalid response")
	}
	statusInfos := make([]*AgentSignStatusInfo, len(statusList))
	for idx, l := range statusList {
		t, ok := l.(map[string]interface{})
		if !ok {
			return nil, errors.New("invalid response")
		}
		i := &AgentSignStatusInfo{
			ProductName: parseString(t["product_name"]),
			ProductCode: parseString(t["product_code"]),
			Status:      parseString(t["status"]),
		}
		i.StatusDesc = i.getStatusDesc()
		statusInfos[idx] = i
	}

	return &AgentSignStatusQueryResp{SignStatusList: statusInfos}, nil
}
